有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

将命令式Java转换为函数式Java(游戏)

我正在学习更多关于Java8及其功能的知识,我想用它做更多的练习。比如说,我有以下命令代码,用于在屏幕边界上画一个圈:

if (circle.getPosition().getX() > width + circle.getRadius()){
    circle.getPosition().setX(-circle.getRadius());
}else if (circle.getPosition().getX() < -circle.getRadius()){
    circle.getPosition().setX(width + circle.getRadius());
}
if (circle.getPosition().getY() > height + circle.getRadius()){
    circle.getPosition().setY(-circle.getRadius());
}else if (circle.getPosition().getY() < -circle.getRadius()){
    circle.getPosition().setY(height + circle.getRadius());
}
  1. 我怎样才能开始尝试“功能化”它?也许是伪代码?在我看来,在这个例子中,易变性和状态似乎是固有的
  2. 函数式编程不适合游戏开发吗?我喜欢两者,所以我试着把它们结合起来

共 (6) 个答案

  1. # 1 楼答案

    你几乎可以用任何编程语言编写函数式代码,但你不能轻松地用任何语言学习函数式编程。Java尤其让函数式编程变得非常痛苦,以至于想要在JVM中进行函数式编程的人想到了Clojure和Scalaz。如果你想学习函数式思维方式(它自然地处理什么问题,如何处理,什么问题更难处理,以及它如何管理它们等等),我强烈建议你花一些时间学习函数式或主要是函数式语言。基于语言质量、易于使用功能性习惯用法、学习资源和社区的综合考虑,我的首选是Haskell,下一个选择是Racket。其他人当然会有其他的看法

  2. # 2 楼答案

    函数式编程适合游戏开发(为什么不呢?)。问题通常更多的是性能和内存消耗,甚至是任何功能性游戏引擎是否能在这些指标上击败现有的非功能性游戏引擎。你不是唯一一个喜欢函数式编程和游戏开发的人。约翰·卡马克似乎也是,看看他在2013年地震大会上的主题演讲。他的笔记herehere甚至提供了如何构建功能性游戏引擎的见解

    除了设置理论基础之外,通常有两个概念在函数编程中被新来者所固有,并且来自实践前景。它们是数据不变性状态缺失。前者意味着数据永远不会改变,而后者意味着每项任务都是在没有先验知识的情况下第一次执行的。 考虑到这一点,您的命令式代码有两个问题:setters改变圆的位置,并且代码依赖于widthheight的外部值(全局状态)。要修复它们,请让函数在每次更新时返回一个新的圆圈,并将屏幕分辨率作为参数。让我们应用the first clue from the video并将对世界静态快照的引用和对正在“更新”的实体的引用传递给更新函数:

    class Circle extends ImmutableEntity {
    
            private int radius;
    
            public Circle(State state, Position position, int radius) {
                super(state, position);
    
                this.radius = radius;
            }
    
            public int getRadius() {
                return radius;
            }
    
            @Override
            public ImmutableEntity update(World world) {
                int updatedX = getPosition().getX();
                if (getPosition().getX() > world.getWidth() + radius){
                    updatedX = -radius;
                } else if (getPosition().getX() < -radius){
                    updatedX = world.getWidth() + radius;
                }
    
                int updatedY = getPosition().getX();
                if (getPosition().getY() > world.getHeight() + radius){
                    updatedY = -radius;
                } else if (getPosition().getY() < -radius) {
                    updatedY = world.getHeight() + radius;
                }
    
                return new Circle(getState(), new Position(updatedX, updatedY), radius);
            }
        }
    
        class Position {
    
            private int x;
            private int y;
    
           //here can be other quantities like speed, velocity etc.
    
            public Position(int x, int y) {
                this.x = x;
                this.y = y;
            }
    
            public int getX() {
                return x;
            }
    
            public int getY() {
                return y;
            }
        }
    
        class State { /*...*/ }
    
        abstract class ImmutableEntity {
    
            private State state;
            private Position position;
    
            public ImmutableEntity(State state, Position position) {
                this.state = state;
                this.position = position;
            }
    
            public State getState() {
                return state;
            }
    
            public Position getPosition() {
                return position;
            }
    
            public abstract ImmutableEntity update(World world);
        }
    
        class World {
            private int width;
            private int height;
    
            public World(int width, int height) {
                this.width = width;
                this.height = height;
            }
    
            public int getWidth() {
                return width;
            }
    
            public int getHeight() {
                return height;
            }
        }
    

    现在棘手的部分是如何影响世界和其他实体的状态。您可以遵循second clue from the video并使用事件传递机制来来回传递此类更改,以便游戏的其余部分了解所有效果

    显然,你只能保留事件,即使在改变圈位置时也完全依赖它们。所以,如果你向你的实体引入某种id,你将能够传递MoveEntity(id, newPosition)

  3. # 3 楼答案

    在这个例子中,对可变性的要求没有任何内在的东西。当务之急是通过应用改变现有圆圈状态的副作用来修改现有圆圈

    函数方法是拥有一个不变的数据结构,并创建一个从第一个结构中获取数据并创建新结构的函数。在您的示例中,函数方法将使圆不可变,即没有setX()或setY()方法

    private Circle wrapCircleAroundBounds(Circle circle, double width, double height) {
        double newx = (circle.getPosition().getX() > width + circle.getRadius()) ? -circle.getRadius() : width + circle.getRadius()
        double newy = (circle.getPosition().getY() > height + circle.getRadius()) ? -circle.getRadius() : height + circle.getRadius()
        return new Circle(newx, newy)
    }
    

    使用Java8的功能特性,您可以想象将一个圆列表映射到包装的圆:

    circles.stream().map(circ -> wrapCircleAroundBounds(circ, width, height))
    

    命令式方法和函数式方法有不同的优点,例如,函数式方法本质上是线程安全的,因为它具有不变性,所以您应该能够更容易地并行这类代码。例如,一个人可以同样安全地写:

    circles.parallelStream().map(circ -> wrapCircleAroundBounds(circ, width, height))
    

    我不认为函数式编程一定不适合游戏开发,但是,尽管已经完成了,但它肯定不是一种标准的方法,因此如果使用函数式语言,就不会获得相同级别的库支持

    正如dfeuer在他的回答中所说,Java的功能特性非常原始——您不支持代数数据类型、模式匹配等,这将使用函数式表达问题变得更加容易(至少在您习惯了这些习惯用法之后)。我同意至少读一点关于Haskell的书,它有一个很好的教程:http://learnyouahaskell.com/chapters是一个很好的开始。与Scala不同,Scala在很大程度上是一种多平台语言,在学习新风格的过程中,您不会依赖OOP功能

  4. # 4 楼答案

    这里没有太多的“功能化”适用。但至少我们可以与mutability作战。 首先pure functions。这将有助于分离logic。使其清晰且易于测试。 回答问题:你的代码是做什么的?它接受一些参数并返回两个参数newxy。 接下来的示例将使用pseudoscala编写

    所以你需要一个函数,在计算x和y的时候调用两次

    def (xOrY: Int, widthOrHeight: Int, radius: Int): Int = {
    
    if (x > widthOrHeight + radius) -1*radius else  widthOrHeight + radius
    // do your calculation here - return x or y values.
    
    }
    

    p.S>;到目前为止,无论您想在哪里应用功能性风格:因为您需要做一些业务逻辑,所以最好使用功能性方法

    但不要试图将其过度复杂化,因为这没有帮助。 因此,对于这个示例,我不会做的是下一步(接下来是伪scala):

    def tryToMakeMove(conditions: => Boolean, move: => Unit) = if (conditions) move()
    /// DO NOT DO IT AT HOME :)
    tryToMakeMove(circle.getPosition().getX() > width + circle.getRadius(),  circle.getPosition().setX(-circle.getRadius())).andThen()
       tryToMakeMove(circle.getPosition().getX() < -circle.getRadius()), circle.getPosition().setX(width + circle.getRadius()))
    ).andThen ... so on.
    

    这就是功能程序的样子。我创建了高阶函数(它接受其他函数作为参数,并在内部调用它)。 有了这些函数,我调用了你必须执行的一对一操作。。。 但这种实用风格并没有真正起到帮助作用。完全您应该只在需要简化代码的地方正确地应用它

  5. # 5 楼答案

    第一点:通过思考代码应该实现什么,您可以“功能化”您的示例。这就是,你有一个圆,想根据一些条件计算另一个圆。但出于某种原因,你的强制性教养让你认为输入圈和输出圈应该存储在相同的记忆位置

    为了发挥功能,首先要忘记记忆位置,接受价值观。用你对intjava.lang.Integer或其他数字类型的相同方式来思考每种类型

    例如,假设某个新手向您展示了如下代码:

    double x = 3.765;
    sin(x);
    System.out.println("The square root of x is " + x);
    

    并抱怨sin似乎不起作用。那你会怎么想

    现在考虑一下:

    Circle myCircle = ....;
    wrapAroundBoundsOfScreen(myCircle);
    System.out.println("The wrapped-around circle is now " + myCircle);
    

    当后一种代码在你看来和前一种代码一样荒谬时,你就已经爬上了函数式编程的第一步。是的,这并不意味着不要使用你正在使用的命令式语言的某些特性,或者极其谨慎地使用它们

  6. # 6 楼答案

    How could I go about trying to "Functionalize" it? Maybe some pseudo-code? It seems to me that mutability and state seem inherent in this example.

    您可以尝试将易变性限制为几个函数,并在函数中使用最终变量(这迫使您使用表达式而不是语句)。这里有一种可能的方法:

    Position wrapCircle(Circle circle, int width, int height) {
      final int radius = circle.getRadius();
      final Position pos = circle.getPosition();
      final int oldX = pos.getX();
      final int x = (oldX > width + radius) ? -radius : (
            (oldX < -radius) ? (width + radius) : oldX);
    
      final int y = // similar
    
      return new Position(x, y);
    }
    
    circle.setPosition(wrapCircle(circle, width, height));
    

    除此之外,我将wrapCircle作为Circle类的一个方法,以获得:

    circle.wrapCircle(width, height);
    

    或者我可以更进一步,定义一个getWrappedCircle方法,它会返回一个新的循环实例:

    Circle getWrappedCircle(width, height) {
      newCircle = this.clone();
      newCircle.wrapCircle(width, height);
      return newCircle();
    }
    

    。。这取决于您打算如何构造代码的其余部分

    提示:在Java中尽可能多地使用final关键字。它会自动产生一种更实用的风格

    Is functional programming not a good fit for game development? I love the both, so I'm trying to combine them.

    纯函数式编程速度较慢,因为它需要大量复制/克隆数据。如果性能很重要,那么您肯定可以尝试混合方法,如上所示

    我建议尽可能多地使用不变性,然后进行基准测试,然后仅在性能关键部分转换为可变性